#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>

// 产生commenKey的两个公共参数
const std::string gpathname = "/home/xiaoqian/git_clone/xiaoqian-Linux/2024-04-27下_共享内存+消息队列+信号量/msg";
const int gproj_id = 0x6;
const int gproj_id_sem = 0x7;
// 1.创建者/使用者打开msg的方式; 2.区分创建者/使用者
const int gCreater = IPC_CREAT | IPC_EXCL | 0664;
const int gUser = IPC_CREAT | 0664;
// 区分读写
const int gWrite = 2;
const int gRead = 1;
// （消息块）msgbuf结构体
const int msgSize = 1024;
struct myMsgbuf
{
    long mtype;
    char mtext[msgSize]; // 1.静态数组是在编译期间就能大小；2.变长数组（不能初始化）实在运行时确定大小
};

class Sem
{
    // key_t CommKeyGet(const std::string &gpathname, const int gproj_id_sem)
    // {
    //     _key = ftok(gpathname.c_str(), gproj_id);
    //     if (_key < 0)
    //     {
    //         perror("ftok fail");
    //         return -1;
    //     }
    //     return _key;
    // }

public:
    Sem(const std::string &gpathname, const int gproj_id, int who)
        : _gpathname(gpathname), _gproj_id(gproj_id), _who(who)
    {
        _key = ftok(_gpathname.c_str(), _gproj_id);
        if (_key < 0)
        {
            perror("ftok fail");
            exit(-1);
        }
        _semid = semget(_key, 1, _who);
        if (_semid < 0)
        {
            perror("semget fail");
            exit(-1);
        }
    }
    ~Sem()
    {
        if (_who == gCreater)
        {
            int res = semctl(_semid, 0, IPC_RMID);
            if (res < -1)
            {
                perror("semctl fail");
                exit(-1);
            }
        }
    }
    int SemopSub()
    {
        _buf.sem_op = -1;
        semop(_semid, &_buf, 2);
    }
    int SemopAdd()
    {
        _buf.sem_op = +1;
        semop(_semid, &_buf, 2);
    }

private:
    sembuf _buf = {0, -1, 0};
    std::string _gpathname;
    int _gproj_id;
    int _who;
    int _key;
    int _semid;
};

class Msg
{

    void MsgGet(key_t key, int who) // who==msg_flg==打开方式   //void MsgGet()
    {
        _msgid = msgget(key, who); // gCreate 没有就创建, 有就报错崩溃退出
        if (_msgid < 0)
        {
            perror("msgget fail");
            exit(-1);
        }
    }

public:
    Msg(const std::string &gpathname, const int gproj_id, int who)
        : _gpathname(gpathname), _gproj_id(gproj_id), _who(who), _sem(gpathname, gproj_id_sem, who)
    {
        // 1.获取commonKey
        _key = ftok(gpathname.c_str(), gproj_id);
        if (_key < 0)
        {
            perror("ftok fail");
            exit(-1);
        }
        // 2.创建msg  //用1.传形参 / 2.直接用属性都行, 传形参能让程序员更清楚的知道11.前面产生的key用在哪了，22.当前函数用了哪些参数
        MsgGet(_key, _who); // who==msg_flg==打开方式  // MsgGet();

        // 3.有一个进程获取了临界资源（消息队列），sem就--
        if (who == gCreater)
        {
            _sem.SemopSub(); // 信号量--
        }
    }
    ~Msg()
    {
        if (_who == gCreater)
        {
            int n = msgctl(_msgid, IPC_RMID, nullptr);
            if (n < 0)
            {
                perror("msgctl fail");
                exit(-1);
            }
        }
        _sem.SemopAdd(); // 信号量++
    }
    int MsgWrite()
    {
        myMsgbuf message;                         // 消息块对象, 要自己对照OS中的，自己设置
        message.mtype = gCreater;                 // 当前消息块是谁发的？
        std::cin.getline(message.mtext, msgSize); // 输入信息

        int res = msgsnd(_msgid, &message, msgSize, 0); // 发送
        if (res == -1)
        {
            perror("msgsnd fail");
            exit(-1);
        }
        // 先发出去，再判断
        if (strcmp(message.mtext, "break") == 0)
        {
            return -2;
        }
        return res;
    }
    const std::string MsgRead()
    {
        myMsgbuf recvBuff; // 消息块对象, 要自己对照OS中的，自己设置
        // recvBuff.mtype = gUser;                                    // 当前消息块是谁发的？
        int res = msgrcv(_msgid, &recvBuff, msgSize, gCreater, 0); // 接收gCreater的消息块
        if (res < 0)                                               // 返回接收到的mtext的字节数
        {
            perror("msgrcv fail");
            exit(-1);
        }
        return recvBuff.mtext;
    }

private:
    Sem _sem;
    std::string _gpathname;
    int _gproj_id;
    int _who;
    int _key;
    int _msgid;
};